package correctness;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;

import model.LogicType;
import model.editormodel.EditorModel;
import model.editormodel.Input;
import model.editormodel.LocsiParser;
import model.editormodel.LogicEditorElement;
import model.editormodel.Output;
import model.simulationmodel.LogicValue;
import model.simulationmodel.SimulationModel;
import model.simulationmodel.logicelements.LogicSimulationElement;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import controller.commoncontroller.UtilityDialogs;

import view.UtilityConstants;

/**
 * Dieser Test erstellt und simuliert eine Schaltung mit allen selbstdefinierten
 * Bauteilen, die hintereinander geschaltet werden.
 * 
 * @author Team Locsi
 * 
 */
public class AllSDGatterTest {
	// benoetigte Variablen fürs Einlesen
	private static File sdirectory;
	private static FilenameFilter filter;

	// maximales Ausmass eines Bauteils
	private final static int RASTER = 100;

	// Objekte die benoetigt werden
	private static EditorModel eModel;
	private static SimulationModel sModel;
	private static LocsiParser locsiParser;
	// Name der Schaltung, der beim abspeichern verwendet wird
	private static String name = "AllSDGatterTest";

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		eModel = new EditorModel();
		sModel = new SimulationModel(eModel);
		locsiParser = new LocsiParser(eModel);

		// erzwingt, dass der componenthandler nur .xml dateien anzeigt
		filter = new FilenameFilter() {

			@Override
			public boolean accept(File dir, String name) {
				return (name.endsWith(UtilityDialogs.XML_EXTENSION));
			}
		};
		sdirectory = new File(UtilityConstants.SD_DIRECTORY);
		List<Input> inputs = new ArrayList<Input>();
		List<Output> outputs = new ArrayList<Output>();
		List<Input> firstInput = new ArrayList<Input>();
		List<Output> firstOutput = new ArrayList<Output>();
		List<Input> lampInput = new ArrayList<Input>();
		List<Output> switchOutput = new ArrayList<Output>();
		List<LogicEditorElement> sdList = new ArrayList<LogicEditorElement>();
		File[] compFiles = sdirectory.listFiles(filter);
		List<String> compNames = new ArrayList<String>();
		String location = "test" + UtilityConstants.SEPARATOR + "model"
				+ UtilityConstants.SEPARATOR + "editormodel"
				+ UtilityConstants.SEPARATOR + "test_saved_circuits"
				+ UtilityConstants.SEPARATOR + name
				+ UtilityDialogs.XML_EXTENSION;

		// einlesen aller vorhandenen selbstdefinierten Gatter
		if (sdirectory.exists()) {
			// von jeder gefundenen .xml datei wird das .xml hinten
			// abgeschnitten
			// und der uebriggebliebene name in die liste der namen eingefuegt
			for (File file : compFiles) {
				String[] splitted = file.getName().split(
						UtilityDialogs.XML_EXTENSION);
				if (splitted.length > 0) {
					compNames.add(splitted[0]);
				}
			}
		}

		double xCoord = 200;
		double yCoord = 200;

		int n = 1;
		// jedes vorhandene selbstdefinierte Bauteil zur Schaltung hin zu fuegen
		for (String compName : compNames) {
			eModel.addLogicElement(LogicType.SELFDEFINED, compName, xCoord
					+ RASTER * n, yCoord);
			++n;
		}

		// nun werden alle Elemente in einer Liste gespeichert, damit deren
		// Reihenfolge fixiert ist und gleichzeitig werden noch die Inputs und
		// Outputs in den jeweiligen Listen vermerkt
		for (LogicEditorElement e : eModel.getCircuitModel()
				.getUnmodifableElementsOnEditor().values()) {
			sdList.add(e);
			inputs.addAll(e.getInputs().values());
			outputs.addAll(e.getOutputs().values());

			// je ein In- und Output eines jeden Elements wird separat
			// referenziert
			firstInput.add(inputs.remove(inputs.size() - 1));
			firstOutput.add(outputs.remove(outputs.size() - 1));
		}

		// nun wird eine Kette gebildet und die restlichen freien Inputs mit
		// Lampen beschalten
		while (firstOutput.size() > 1) {
			eModel.setConnection(firstOutput.remove(0), firstInput.remove(1));
		}
		eModel.setConnection(firstOutput.remove(0), firstInput.remove(0));

		// nun werden für alle unbesetzten Inputs Schalter und für alle
		// unbesetzten Outputs Lampen hinzugefügt
		n = 0;
		for (int i = 0; i < inputs.size(); ++i) {
			eModel.addLogicElement(LogicType.SWITCH, null, xCoord + RASTER * n,
					yCoord + 200);
			++n;
		}
		n = 0;
		for (int o = 0; o < outputs.size(); ++o) {
			eModel.addLogicElement(LogicType.LAMP, null, xCoord + RASTER * n,
					yCoord + 400);
			++n;
		}
		for (LogicEditorElement e : eModel.getCircuitModel()
				.getUnmodifableElementsOnEditor().values()) {
			if (e.getType() == LogicType.SWITCH) {
				switchOutput.add(e.getOutputs().get(Output.STD_O));
			}
			if (e.getType() == LogicType.LAMP) {
				lampInput.add(e.getInputs().get(Input.STD_A));
			}
		}

		// werden diese neuen Elemente eingebunden
		while (!inputs.isEmpty()) {
			eModel.setConnection(switchOutput.remove(0), inputs.remove(0));
		}
		while (!outputs.isEmpty()) {
			eModel.setConnection(outputs.remove(0), lampInput.remove(0));
		}

		// damit im Falle eines Fehlers dieser leichter gefunden werden kann
		// wird die Schaltung gespeichert
		locsiParser.saveCircuit(name, location, eModel.getCircuitModel());
	}

	@Before
	public void setUp() throws Exception {

	}

	/**
	 * Diese Methode testet, ob die in der setUpBeforeClass()-Methode erzeugte
	 * Schaltung korrekt im EditorModel und dem dazugehoerigen CircuitModel
	 * vorhanden ist
	 */
	@Test
	public void testEditorModelComponents() {
		// testen ob alle bauteile im zum editormodel gehoerenden circuitmodel
		// vorhanden sind

	}

	/**
	 * Diese Methode testet, ob der Wechsel in den SimulationModus korrekt
	 * verlaufen ist. Dazu wird ueberprueft, ob die Bauteile korrekt ins
	 * SimulationModel geladen wurden
	 */
	@Test
	public void testChangeToSimulationMode() {
		// testen ob die bauteile im simulationmodel vollständig erzeugt werden
		sModel.updateSimulationElements();
		assertTrue(
				"Anzahl der zu simulierenden Elemente stimmt nicht mit den Elementen im Bearbeitungsbereich überein",
				eModel.getCircuitModel().getUnmodifableElementsOnEditor()
						.size() == sModel.getUpperSimulationElements().size());
	}

	/**
	 * Diese Methode testet ob alle selbstdefinierten Bauteile ihre
	 * Eigenschaften eines selbstdefinierten Bauteils im Verbund beibehalten
	 * haben und es werden alle Elemente einmal berechnet. Ausserdem muss es
	 * sich nach Konstruktion um ein Subnetz handeln.
	 */
	@Test
	public void testSimulation() {
		List<LogicSimulationElement> sList = new ArrayList<LogicSimulationElement>();
		// pruefen ob die Schaltung alle Checks zum simulieren besteht
		assertFalse("Leere Schaltung", eModel.isEmptyCircuit());
		assertTrue("Es sind nicht alle Eingaenge verbunden", eModel
				.getCircuitModel().getNumberOfUnrelatedInputs() == 0);
		assertTrue(
				"Die Schaltung erfuellt nicht die Eigenschaften eines selbstdefinierten Bauteils",
				eModel.getCircuitModel().isComponent());

		// testen der simulation
		sModel.updateSimulationElements();
		for (LogicSimulationElement s : sModel.getUpperSimulationElements()
				.values()) {
			if (s.getLogicEditorElement().getType() == LogicType.SWITCH) {
				sList.add(s);

				// erzeugen einer zufälligen Anfangsbelegung
				if ((int) (Math.random() * 100) % 2 == 0) {
					s.setInputValue(Input.STD_A, LogicValue.L1);
				}
				s.calcLogicValue();
			}
			sModel.calcElementsToCalc();
		}
		assertTrue("Die Anzahl der Subnetze ist ungleich 1", eModel
				.getCircuitModel().getNumberOfSubnets().get(0) == 1);
	}
}
